#pragma once
#include <d3d9.h>
#include <d3dx9.h>
#include "TwEngineDx.h"
 

 
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)
#define SAFE_RELEASE(p){if(p) {(p)->Release(); (p)=NULL;}}
#define SAFE_DELETE(p) {if(p) {delete(p); (p)=NULL;}}
#define SAFE_DELETE_ARRAY(p) {if(p) {delete[](p); (p)=NULL;}}

extern int MaxFrames ;    // the highest number of frames a mesh container can be affected by

class MeshAllocation;

struct CUSTOM_FRAME : public D3DXFRAME
{
	D3DXMATRIX CombTransformationMatrix;    // transform data multiplied by all parents' data
};

struct CUSTOM_MESHCONTAINER : public D3DXMESHCONTAINER
{
	D3DXMATRIX** ppFrameMatrices;    // an array of matrices to store current animation data
	LPD3DXMESH pFinalMesh;    // a duplicate of the mesh, representing it's current pose
	LPDIRECT3DTEXTURE9* pTextures;    // an array of applicable textures
};


class TwXfile
{
public:
	TwXfile(void);
	~TwXfile(void);

private:

	LPD3DXFRAME TopFrame;    // the top-most frame of the model
	LPD3DXANIMATIONCONTROLLER AnimationController;    // an object that handles animation
	D3DXMATRIX* FinalMatrices;    // a buffer for the matrices in their final state

public:
	void render_frame(void);    // renders a single frame
	void init_graphics(void);    // 3D declarations
	void init_light(void);    // sets up the light and the material
	void link_frames(CUSTOM_FRAME* pFrame);
	void update_frames(CUSTOM_FRAME* pFrame, D3DXMATRIX* pParentMatrix);
	void update_mesh_containers(CUSTOM_FRAME* pFrame);
	void draw_mesh(CUSTOM_FRAME* pFrame);
};


class MeshAllocation : public ID3DXAllocateHierarchy
{
	STDMETHOD(CreateFrame)(LPCSTR Name, LPD3DXFRAME* ppNewFrame)
	{
		// Create and initialize the custom frame struct
		CUSTOM_FRAME* pFrame = new CUSTOM_FRAME;
		*ppNewFrame = pFrame;
		ZeroMemory(pFrame, sizeof(CUSTOM_FRAME));

		// Copy the name
		if(Name)
		{
			UINT len = strlen(Name) + 1;
			pFrame->Name = new char[len];
			memcpy(pFrame->Name, Name, len);
		}

		return S_OK;
	}

	STDMETHOD(CreateMeshContainer)(LPCSTR Name,
		CONST D3DXMESHDATA* pMeshData,
		CONST D3DXMATERIAL* pMaterials,
		CONST D3DXEFFECTINSTANCE* pEffectInstances,
		DWORD NumMaterials,
		CONST DWORD* pAdjacency,
		LPD3DXSKININFO pSkinInfo,
		LPD3DXMESHCONTAINER* ppNewMeshContainer)
	{
		// Create and initialize the custom mesh container
		struct CUSTOM_MESHCONTAINER *pMeshContainer = new CUSTOM_MESHCONTAINER;
		*ppNewMeshContainer = pMeshContainer;
		ZeroMemory(pMeshContainer, sizeof(CUSTOM_MESHCONTAINER));

		// Check to make sure its a normal mesh
		if(pMeshData->Type != D3DXMESHTYPE_MESH)
		{
			DestroyMeshContainer(pMeshContainer);
			return E_FAIL;
		}

		// Copy the name
		if(Name)
		{
			UINT len = strlen(Name) + 1;
			pMeshContainer->Name = new char[len];
			memcpy(pMeshContainer->Name, Name, len);
		}

		// Copy the mesh data
		pMeshContainer->MeshData.Type = pMeshData->Type;
		pMeshContainer->MeshData.pMesh = pMeshData->pMesh;
		pMeshContainer->MeshData.pMesh->AddRef();

		// Copy the materials
		pMeshContainer->pMaterials = new D3DXMATERIAL[NumMaterials];
		for(DWORD i = 0; i < NumMaterials; i++)
		{
			pMeshContainer->pMaterials[i] = pMaterials[i];
			pMeshContainer->pMaterials[i].MatD3D.Ambient = pMaterials[i].MatD3D.Diffuse;
		}

		// Don't bother with the effect instance
		pMeshContainer->pEffects = NULL;

		// Copy the number of materials
		pMeshContainer->NumMaterials = max(1, NumMaterials);

		// Copy the adjacency data
		pMeshContainer->pAdjacency = new DWORD[pMeshData->pMesh->GetNumFaces() * 3];
		memcpy(pMeshContainer->pAdjacency,
			pAdjacency,
			sizeof(DWORD) * pMeshData->pMesh->GetNumFaces() * 3);

		// Only do these steps if there is skin info
		if(pSkinInfo)
		{
			// Copy the skin data
			pMeshContainer->pSkinInfo = pSkinInfo;
			pSkinInfo->AddRef();

			// Allocate TwMemory for the pointers to the frame matrices
			pMeshContainer->ppFrameMatrices = new D3DXMATRIX*[pSkinInfo->GetNumBones()];
			for(DWORD i = 0; i < pSkinInfo->GetNumBones(); i++)
			{
				pMeshContainer->ppFrameMatrices[i] = NULL;
			}
		}

		// Create a duplicate of the mesh
		pMeshContainer->MeshData.pMesh->CloneMesh(D3DXMESH_MANAGED,
			NULL,
			g_Render->GetDevice(),
			&pMeshContainer->pFinalMesh);

		// Load the textures for each material (if any)
		pMeshContainer->pTextures = new LPDIRECT3DTEXTURE9[pMeshContainer->NumMaterials];
		for(DWORD i = 0; i < NumMaterials; i++)
		{
			pMeshContainer->pTextures[i] = 0;
			if(pMaterials[i].pTextureFilename)
			{
				D3DXCreateTextureFromFileA(g_Render->GetDevice(),
					pMeshContainer->pMaterials[i].pTextureFilename,
					&pMeshContainer->pTextures[i]);
			}
		}

		// Find the maximum number of frames
		if(pMeshContainer->pSkinInfo)
			MaxFrames = max(MaxFrames, (int)pMeshContainer->pSkinInfo->GetNumBones());

		return S_OK;
	}

	STDMETHOD(DestroyFrame)(LPD3DXFRAME pFrameToFree)
	{
		// free the name and the frame
		SAFE_DELETE_ARRAY(pFrameToFree->Name);
		SAFE_DELETE(pFrameToFree);

		return S_OK;
	}

	STDMETHOD(DestroyMeshContainer)(LPD3DXMESHCONTAINER pMeshContainerToFree)
	{
		// cast the pointer to a CUSTOM_MESHCONTAINER
		CUSTOM_MESHCONTAINER* pMeshContainer = (CUSTOM_MESHCONTAINER*)pMeshContainerToFree;

		// free or release all the allocated TwMemory
		SAFE_DELETE_ARRAY(pMeshContainer->Name);
		SAFE_RELEASE(pMeshContainer->MeshData.pMesh);
		SAFE_RELEASE(pMeshContainer->pFinalMesh);
		SAFE_DELETE_ARRAY(pMeshContainer->pMaterials);
		SAFE_DELETE_ARRAY(pMeshContainer->pAdjacency);
		SAFE_RELEASE(pMeshContainer->pSkinInfo);
		SAFE_DELETE_ARRAY(pMeshContainer->ppFrameMatrices);
		for(UINT i = 0; i < pMeshContainer->NumMaterials; i++)
			SAFE_RELEASE(pMeshContainer->pTextures[i]);
		SAFE_DELETE(pMeshContainer);

		return S_OK;
	}
};


